<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./MarchingCubesData.js"></script>

    <style>
      #canvas {
        background-color: black;
        height: 800px;
        width: 800px;
      }
    </style>
  </head>
  <body>
    <div id="canvas"></div>
    <script type="importmap">
      {
        "imports": {
          "three/addons/": "../../node_modules/three/examples/jsm/",
          "three": "../../node_modules/three/build/three.module.js"
        }
      }
    </script>
    <script type="module">
      import * as THREE from 'three';
      import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
      import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';

      import ThreeBase from '../ThreeBase.js';

      function clamp(val, min, max) {
        return val < min ? min : val > max ? max : val;
      }
      function mix(min, max, a) {
        return min + a * (max - min);
      }
      /**
       * a 当前distance
       * b 之前的distance
       * k 平缓过渡的值
       * */
      function smin(a, b, k) {
        let h = clamp(0.5 + (0.5 * (b - a)) / k, 0.0, 1.0);
        return mix(b, a, h) - k * h * (1.0 - h);
      }
      function smooth(d) {
        return Math.exp(-d);
      }
      function lerpPoint(points, values, isolevel, pos) {
        const p1 = pos[0];
        const p2 = pos[1];
        const v1 = values[p1],
          v2 = values[p2];
        const mu = (isolevel - v1) / (v2 - v1);
        return points[p1].clone().lerp(points[p2], mu);
      }

      function marchingCubes(points, values, isolevel) {
        // assumes the following global values have been defined:
        //   THREE.edgeTable, THREE.triTable

        var size = Math.round(Math.pow(values.length, 1 / 3));
        var size2 = size * size;
        var size3 = size * size * size;

        // Vertices may occur along edges of cube, when the values at the edge's endpoints
        //   straddle the isolevel value.
        // Actual position along edge weighted according to function values.
        var vlist = new Array(12);

        const vertex = [];
        const uvs = [];
        const normals = [];

        let tag = true;
        for (var z = 0; z < size - 1; z++)
          for (var y = 0; y < size - 1; y++)
            for (var x = 0; x < size - 1; x++) {
              // index of base point, and also adjacent points on cube
              var p = x + size * y + size2 * z, //(0,0,0)
                px = p + 1, //(1,0,0)
                py = p + size, //(0,1,0)
                pxy = py + 1, //(1,1,0)
                pz = p + size2, //(0,0,1)
                pxz = px + size2, //(1,0,1)
                pyz = py + size2, //(0,1,1)
                pxyz = pxy + size2; //(1,1,1)

              const pos = [p, px, py, pxy, pz, pxz, pyz, pxyz];

              // store scalar values corresponding to vertices
              var value0 = values[p],
                value1 = values[px],
                value2 = values[py],
                value3 = values[pxy],
                value4 = values[pz],
                value5 = values[pxz],
                value6 = values[pyz],
                value7 = values[pxyz];

              // place a "1" in bit positions corresponding to vertices whose
              //   isovalue is less than given constant.

              var cubeindex = 0;
              if (value0 < isolevel) cubeindex |= 1;
              if (value1 < isolevel) cubeindex |= 2;
              if (value2 < isolevel) cubeindex |= 8;
              if (value3 < isolevel) cubeindex |= 4;
              if (value4 < isolevel) cubeindex |= 16;
              if (value5 < isolevel) cubeindex |= 32;
              if (value6 < isolevel) cubeindex |= 128;
              if (value7 < isolevel) cubeindex |= 64;

              // bits = 12 bit number, indicates which edges are crossed by the isosurface
              var bits = edgeTable[cubeindex];

              // if none are crossed, proceed to next iteration
              if (bits === 0) continue;

              // check which edges are crossed, and estimate the point location
              //    using a weighted average of scalar values at edge endpoints.
              // store the vertex in an array for use later.
              var mu = 0.5;

              // bottom of the cube
              if (bits & 1) {
                mu = (isolevel - value0) / (value1 - value0);
                vlist[0] = points[p].clone().lerp(points[px], mu);
              }
              if (bits & 2) {
                mu = (isolevel - value1) / (value3 - value1);
                vlist[1] = points[px].clone().lerp(points[pxy], mu);
              }
              if (bits & 4) {
                mu = (isolevel - value2) / (value3 - value2);
                vlist[2] = points[py].clone().lerp(points[pxy], mu);
              }
              if (bits & 8) {
                mu = (isolevel - value0) / (value2 - value0);
                vlist[3] = points[p].clone().lerp(points[py], mu);
              }
              // top of the cube
              if (bits & 16) {
                mu = (isolevel - value4) / (value5 - value4);
                vlist[4] = points[pz].clone().lerp(points[pxz], mu);
              }
              if (bits & 32) {
                mu = (isolevel - value5) / (value7 - value5);
                vlist[5] = points[pxz].clone().lerp(points[pxyz], mu);
              }
              if (bits & 64) {
                mu = (isolevel - value6) / (value7 - value6);
                vlist[6] = points[pyz].clone().lerp(points[pxyz], mu);
              }
              if (bits & 128) {
                mu = (isolevel - value4) / (value6 - value4);
                vlist[7] = points[pz].clone().lerp(points[pyz], mu);
              }
              // vertical lines of the cube
              if (bits & 256) {
                mu = (isolevel - value0) / (value4 - value0);
                vlist[8] = points[p].clone().lerp(points[pz], mu);
              }
              if (bits & 512) {
                mu = (isolevel - value1) / (value5 - value1);
                vlist[9] = points[px].clone().lerp(points[pxz], mu);
              }
              if (bits & 1024) {
                mu = (isolevel - value3) / (value7 - value3);
                vlist[10] = points[pxy].clone().lerp(points[pxyz], mu);
              }
              if (bits & 2048) {
                mu = (isolevel - value2) / (value6 - value2);
                vlist[11] = points[py].clone().lerp(points[pyz], mu);
              }

              // 获取连接关系表
              const t = triTable[cubeindex];

              for (let i = 0; t[i] != -1; i += 3) {
                var p1 = vlist[t[i]].toArray();
                var p2 = vlist[t[i + 1]].toArray();
                var p3 = vlist[t[i + 2]].toArray();
                // p1,p2,p3三点确定一个法向量
                // e3=p2-p1
                // e1=p3-p2
                // 法向量为
                // (e3*e1)/|(e3*e1)|

                vertex.push(...p1, ...p2, ...p3);
                uvs.push(0, 0, 0, 1, 1, 1);
                const e3 = new THREE.Vector3(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]);
                const e1 = new THREE.Vector3(p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2]);
                const n = e3.multiply(e1).normalize();
                normals.push(...n.toArray(), ...n.toArray(), ...n.toArray());
              }
            }

        return [vertex, uvs, normals];
      }

      function addPlane(points, values, y) {
        for (var i = 0; i < values.length; i++) {
          var d = points[i].y;
          values[i] += smooth(d);
          // values[i] += smin(d, values[i], 1.0);
        }
      }
      function addBall(points, values, config) {
        for (var i = 0; i < values.length; i++) {
          let d = 0;
          const p = points[i];
          config.forEach((item) => {
            if (typeof item === 'number') {
              d += smooth(p.y - item);
            } else if (Array.isArray(item)) {
              const a = item[1] - item[0].distanceToSquared(p);
              d += smooth(a * a);
            }
          });
          values[i] = d;
        }
      }

      class MyEarth extends ThreeBase {
        constructor() {
          super();
          this.initCameraPos = [0, 0, 10];
          this.isAxis = true;
          this.isolevel = 0.5;
          this.step = 0.1;
          this.posy = 0;
        }

        createChart(that) {
          const light = new THREE.AmbientLight(0xffffff); // soft white light
          this.scene.add(light);
          const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
          directionalLight.position.set(3, 3, 3);
          this.scene.add(directionalLight);
          const axisMin = -5;
          const axisMax = 5;
          const axisRange = axisMax - axisMin;

          const size = 30;
          const size2 = size * size;
          const size3 = size * size * size;

          const points = [];
          this.points = points;
          // generate the list of 3D points
          for (var k = 0; k < size; k++)
            for (var j = 0; j < size; j++)
              for (var i = 0; i < size; i++) {
                var x = axisMin + (axisRange * i) / (size - 1);
                var y = axisMin + (axisRange * j) / (size - 1);
                var z = axisMin + (axisRange * k) / (size - 1);

                points.push(new THREE.Vector3(x, y, z));
              }
          const values = new Array(points.length).fill(0);
          this.values = values;
          addBall(this.points, this.values, [
            [new THREE.Vector3(0, 3.5, 0), 1.0],
            [new THREE.Vector3(0, 0, 0), 1.5],
            [new THREE.Vector3(-1, -1, 0), 1.5],
            -2
          ]);
          const [position, uv, normal] = marchingCubes(points, values, 0.5);
          const geometry = new THREE.BufferGeometry();
          geometry.setAttribute(
            'position',
            new THREE.BufferAttribute(new Float32Array(position), 3)
          );
          geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uv), 2));
          geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(normal), 3));

          this.geometry = geometry;
          const texture = new THREE.TextureLoader().load('../assets/angra.jpg');

          texture.mapping = THREE.EquirectangularReflectionMapping;

          this.scene.background = texture;

          const material = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            // envMap: texture,
            // roughness: 0.1,
            // metalness: 1.0,
            // refractionRatio: 0.5,
            flatShading: true
          });
          const mesh = new THREE.Mesh(geometry, material);
          this.scene.add(mesh);
        }
        resetValues() {
          for (let i = 0; i < this.values.length; i++) {
            this.values[i] = 0;
          }
        }
        animateAction() {
          if (this.geometry) {
            if (this.posy < -1) {
              this.step = 0.01;
            } else if (this.posy > 3) {
              this.step = -0.01;
            }
            this.posy += this.step;
            this.resetValues();
            addBall(this.points, this.values, [
              [new THREE.Vector3(0, 3.5, 0), 1.0],
              [new THREE.Vector3(0, this.posy, 0), 1.0],
              [new THREE.Vector3(-1, -1, 0), 1.5],
              -2
            ]);

            const [position, uv, normal] = marchingCubes(this.points, this.values, 0.5);

            this.geometry.setAttribute(
              'position',
              new THREE.BufferAttribute(new Float32Array(position), 3)
            );
            this.geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uv), 2));
            this.geometry.setAttribute(
              'normal',
              new THREE.BufferAttribute(new Float32Array(normal), 3)
            );
          }
        }
      }

      var myEarth = new MyEarth();
      window.myEarth = myEarth;
      myEarth.initThree(document.getElementById('canvas'));
      myEarth.createChart();
    </script>
  </body>
</html>
